#include "asterisk.h"
#include "expat.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision: $")

#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/translate.h"
#include "asterisk/utils.h"
#include "asterisk/options.h"
#include "asterisk/app.h"
#include "asterisk/cli.h"
#include "asterisk/localtime.h"
#include "asterisk/say.h"

#ifdef XML_LARGE_SIZE
#if defined(XML_USE_MSC_EXTENSIONS) && _MSC_VER < 1400
#define XML_FMT_INT_MOD "I64"
#else
#define XML_FMT_INT_MOD "ll"
#endif
#else
#define XML_FMT_INT_MOD "l"
#endif

// ASTERISK module-related vars
static char *app_ivm = "ivm";
static char *ivm_synopsis = "Synopsis for my ivm";
static char *ivm_descrip = "Description for my ivm";


typedef unsigned long       DWORD;
typedef int                 BOOL;
#define FALSE	0
#define TRUE	1
// CONFIG STRUCTURES
typedef struct _PLAYFILE_STRUCT
{
	BOOL bUsed;
	char path[256];
	BOOL bRepeat;
}PLAYFILE_STRUCT;
typedef struct _ENDPOINT_STRUCT
{
	char abonent[256];
}ENDPOINT_STRUCT;

typedef struct _MENULEVEL_STRUCT MENULEVEL_STRUCT;

typedef struct _MENUITEM_STRUCT
{
	DWORD dwNum;
	char playfile_path[256];
	MENULEVEL_STRUCT* menuLevelItem; 
	ENDPOINT_STRUCT* endpointItem; 

}MENUITEM_STRUCT;

struct _MENULEVEL_STRUCT
{
	DWORD dwItemsNum;
	MENUITEM_STRUCT* items;
};

typedef struct _CONFIG_STRUCT
{
	PLAYFILE_STRUCT pf_hello;
	PLAYFILE_STRUCT pf_select;
	PLAYFILE_STRUCT pf_redirecting;
	PLAYFILE_STRUCT pf_sorry;
	PLAYFILE_STRUCT pf_timeout;
	PLAYFILE_STRUCT pf_internalError;
	BOOL bRepeatAllOnTimeout;
	MENULEVEL_STRUCT menuLevel;
	ENDPOINT_STRUCT* defaultEndpoint;
	DWORD dwShortTimeout;
	DWORD dwLongTimeout;
}CONFIG_STRUCT;

// function declarations
static int call_redirect(struct ast_channel *chan, void *data);
static int playFile(struct ast_channel *chan, void *file);
static int ParseXMLConfig(CONFIG_STRUCT* cfg,char* xmlPath);
static void ClearConfigMenuData(MENULEVEL_STRUCT* cfg);
int ProposeLevelAndListen(struct ast_channel *chan, CONFIG_STRUCT* cfg, MENULEVEL_STRUCT* level,MENULEVEL_STRUCT** nextLevel,BOOL* bEndpointChosen,BOOL* bTimeouted,char** chosenEndpoint);
static int tryGetUserInput(struct ast_channel *chan, DWORD dwTimeout,DWORD* lpDwUserInput);

// asterisc load-unload module functions
static int ivm_exec(struct ast_channel *chan, void *data);
static int OnUnLoad(void)
{
	ast_log(LOG_NOTICE, "Goodbye World!\n");
	printf("Unloading Hello World!\n");
	int res;
	res = ast_unregister_application(app_ivm);
	ast_module_user_hangup_all();

	return res;
}
static int OnLoad(void)
{
	ast_log(LOG_NOTICE, "My IVM!\n");
	printf("Loading My IVM!\n");
	return ast_register_application(app_ivm, ivm_exec, ivm_synopsis, ivm_descrip);
}

// main ENTRY-POINT
static int ivm_exec(struct ast_channel *chan, void *data)
{
	// declare local vars
	DWORD dwRc = 0;
	char chBuf[128];
	char *inParams;
	char* xmlPath = 0;// example: "/home/user/Desktop/WORK/asterisk-1.4.24/xml/test.xml";
	CONFIG_STRUCT cfg;
	BOOL bEndpointChosen = FALSE;
	BOOL bTimeouted	= FALSE;
	char *chRedirectionPoint = NULL;


	memset(chBuf,0,sizeof(chBuf));

	// add user
	struct ast_module_user *u;
	u = ast_module_user_add(chan);
	
	// convert app vars
	if (ast_strlen_zero(data)) { /* no input data available */
		ast_log(LOG_WARNING, "IVM requires an argument\n");
		return -1;
	}
	inParams = ast_strdupa(data);


	// read settings form  XML
	memset(&cfg,0,sizeof(cfg));
	xmlPath = inParams;
	dwRc = ParseXMLConfig(&cfg,xmlPath);
	if(dwRc != 0)
	{
		// failure
		goto FAILED_0;
	}
	// hang up the call
	dwRc = ast_answer(chan);
	if(dwRc != 0)
	{
		// failure
		goto FAILED_0;
	}

	if(cfg.pf_hello.bUsed)
	{
		// play file : "hello"
		dwRc = playFile(chan,cfg.pf_hello.path);
		if(dwRc != 0)
		{
			// failure
			goto FAILED_1;
		}
	}
	

MAINLOOP:

	chRedirectionPoint = NULL;
	BOOL bLevelLoopCount = 0;
	MENULEVEL_STRUCT* lpCurLevel = &cfg.menuLevel;
	MENULEVEL_STRUCT* lpNextLevel = NULL;
	do
	{
		if(cfg.pf_select.bUsed)
		{
			if((bLevelLoopCount == 0)||(cfg.pf_select.bRepeat))
			// play file : "Select option"
			dwRc = playFile(chan,cfg.pf_select.path);	
			if(dwRc != 0)
			{
				// failure
				goto FAILED_1;
			}
		}

		dwRc = ProposeLevelAndListen(chan,&cfg,lpCurLevel,&lpNextLevel,&bEndpointChosen,&bTimeouted,&chRedirectionPoint);
		if(dwRc != 0)
		{
			// failure
			goto FAILED_1;
		}
		lpCurLevel = lpNextLevel;
		lpNextLevel = NULL;
		bLevelLoopCount = bLevelLoopCount + 1;
		
	}while(!bEndpointChosen&&!bTimeouted);


	if(bTimeouted)
	{
		// timeout
		if(cfg.defaultEndpoint != 0)
		{
			// default endpoint is set, use it
			bEndpointChosen = TRUE;
			bTimeouted = FALSE;
			chRedirectionPoint = cfg.defaultEndpoint->abonent;
		}
		else
		{
			// no default endpoint, timeout
			if(cfg.bRepeatAllOnTimeout)
			{
				// repeat all
				goto MAINLOOP;
			}
			else
			{
				// end
				if(cfg.pf_timeout.bUsed)
				{
					// play file : "timeout"
					dwRc = playFile(chan,cfg.pf_timeout.path);	
					if(dwRc != 0)
					{
						// failure
						goto FAILED_1;
					}
				}
				goto END;
			}
		}
	}

	if(bEndpointChosen)
	{
		// chosen redirection point
		if(cfg.pf_redirecting.bUsed)
		{
			// play file : "redirecting"
			dwRc = playFile(chan,cfg.pf_redirecting.path);	
			if(dwRc != 0)
			{
				// failure
				goto FAILED_1;
			}
		}

		dwRc = call_redirect(chan,chRedirectionPoint);
		if(dwRc == 0)
		{
			// all done!
			goto END;
		}
		else
		{
			// failed
			if(cfg.pf_sorry.bUsed)
			{
				// play file : "sorry"
				dwRc = playFile(chan,cfg.pf_sorry.path);	
				if(dwRc != 0)
				{
					// failure
					goto FAILED_1;
				}
			}
			goto END;
		}
	}
	
FAILED_1:	// get here in case of failure, trying to at least inform user of error
	if(cfg.pf_internalError.bUsed)
	{
		// play file : "internalError"
		dwRc = playFile(chan,cfg.pf_internalError.path);	
		if(dwRc != 0)
		{
			// failure
			// ignore
		}
	}
FAILED_0:	// get here in case of TOTAL failure
	// can't recover
END:


	// remove user	
	ast_module_user_remove(u);

	if(cfg.defaultEndpoint)
		free(cfg.defaultEndpoint);
	ClearConfigMenuData(&(cfg.menuLevel));
	return (int)dwRc;

}

int ProposeLevelAndListen(struct ast_channel *chan, CONFIG_STRUCT* cfg, MENULEVEL_STRUCT* level,MENULEVEL_STRUCT** nextLevel,BOOL* bEndpointChosen,BOOL* bTimeouted,char** chosenEndpoint)
{
	DWORD dwRc = 0;
	DWORD dwTimeout = 0;
	DWORD dwUserInput = 10; // 0-9 allowed
	int i = 0;
	int j = 0;
	

	for(i = 0; i < level->dwItemsNum; i++)
	{
		BOOL bLastItem = (i == level->dwItemsNum-1);
		MENUITEM_STRUCT* curMenuItem = &level->items[i];

		// name a menu element
		dwRc = playFile(chan,curMenuItem->playfile_path);	
		if(dwRc != 0)
		{
			// failure
			goto ONERROR;
		}

		// short timeout on try accept input or long timeout if it's the last one
		if(bLastItem)
			dwTimeout = cfg->dwLongTimeout;
		else
			dwTimeout = cfg->dwShortTimeout;

		dwRc = tryGetUserInput(chan,dwTimeout,&dwUserInput);
		if(dwRc != 0)
		{
			// no input yet
			if(bLastItem)
			{
				// timeout
				*bTimeouted = TRUE;
				*bEndpointChosen = FALSE;
			}
		}
		else
		{
			// input accepted!
			*bTimeouted = FALSE;
			MENUITEM_STRUCT* selectedMenuItem = 0;

			for(j = 0; j < level->dwItemsNum; j++)
			{
				if(level->items[j].dwNum == dwUserInput)
				{
					selectedMenuItem = &level->items[j];
					break;
				}
			}			
			if(selectedMenuItem != 0)
			{
				if(selectedMenuItem->endpointItem != NULL)
				{
					*bEndpointChosen = TRUE;
					*chosenEndpoint = selectedMenuItem->endpointItem->abonent;
				}
				else
				{
					*bEndpointChosen = FALSE;
					*nextLevel = selectedMenuItem->menuLevelItem;
				}
				return 0;
			}
			else
			{
				// the pressed button does not correspond to any available endpoints or levels
				// (FOR NOW) consider this as timeout error
				*bTimeouted = TRUE;
				*bEndpointChosen = FALSE;
			}
		}
	}

	return 0;
ONERROR:
	dwRc = 1;
	return (int)dwRc;
}
static int tryGetUserInput(struct ast_channel *chan, DWORD dwTimeout,DWORD* lpDwUserInput)
{
	DWORD dwRc = 0;
	
	int waitRc = ast_waitfordigit(chan, dwTimeout);
	if(waitRc < 0)
	{
		// error
		dwRc = -1;
	}else
	if(waitRc == 0)
	{
		// no input
		dwRc = -2;
	}
	else
	{
		// digit aquired
		*lpDwUserInput = (DWORD)(waitRc-48);
	}
	return (int)dwRc;
}
static int playFile(struct ast_channel *chan, void *file)
{
	DWORD dwRc = 0;
	dwRc = ast_streamfile(chan, file, "en");
	if (dwRc == 0) 
	{ 
		dwRc = ast_waitstream(chan, "");	
		ast_stopstream(chan);
	} 
	else 
	{
		ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name);
	}
	return (int)dwRc;
}
static int call_redirect(struct ast_channel *chan, void *data)
{
	char chBuf[128];
	memset(chBuf,0,sizeof(chBuf));
	int res;
	int len;
	struct ast_module_user *u;
	char *slash;
	char *tech = NULL;
	char *dest = NULL;
	char *status;
	char *parse;
	int priority_jump = 0;
	AST_DECLARE_APP_ARGS(args,
		AST_APP_ARG(dest);
		AST_APP_ARG(options);
	);

	// too lazy to remove.. it still converts the correct input string into correct output
	if (ast_strlen_zero((char *)data)) {
		ast_log(LOG_WARNING, "Transfer requires an argument ([Tech/]destination[|options])\n");
		ast_module_user_remove(u);
		pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "FAILURE");
		return 0;
	} else
		parse = ast_strdupa(data);

	AST_STANDARD_APP_ARGS(args, parse);

	if (args.options) {
		if (strchr(args.options, 'j'))
			priority_jump = 1;
	}

	dest = args.dest;

	if ((slash = strchr(dest, '/')) && (len = (slash - dest))) {
		tech = dest;
		dest = slash + 1;
		/* Allow execution only if the Tech/destination agrees with the type of the channel */
		if (strncasecmp(chan->tech->type, tech, len)) {
			pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "FAILURE");
			//ast_module_user_remove(u);
			return 0;
		}
	}

	/* Check if the channel supports transfer before we try it */
	if (!chan->tech->transfer) {
		pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "UNSUPPORTED");
		//ast_module_user_remove(u);
		return 0;
	}

	res = ast_transfer(chan, dest);

	if (res < 0) {
		status = "FAILURE";
		if (priority_jump || ast_opt_priority_jumping)
			ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
		res = 0;
	} else {

		status = "SUCCESS";
		res = 0;
	}

	
	pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", status);

	return res;
}

// XML PARSING AND EXPAT CALLBACKS
static void XMLCALL XML_startElement(void *userData, const char *name, const char **atts);
static void XMLCALL XML_endElement(void *userData, const char *name);
static void XMLCALL XML_chardata(void *userData, const XML_Char *s, int len);

// for unix build
//#define stricmp strcasecmp

typedef struct _PARSERDATA_STRUCT
{
	int depth;
	CONFIG_STRUCT* cfg;
	BOOL bFailed;
	
	//  level 1 tags
	BOOL bConfigStarted;
	BOOL bMainMenuItemListStarted;
	BOOL bDefaultEndpointStarted;

	// level 2 tags
	void* lpCurrentConfigItem;

	// level 3 tags
	MENULEVEL_STRUCT* lpCurMenuLevel;
	MENUITEM_STRUCT* lpCurrentMenuItem;

	// menu levels chain
	int menuLevelNum;
	int menuLevelLastDepth[1024];
	MENULEVEL_STRUCT* menuLevelsStack[1024];
	MENUITEM_STRUCT* menuItemsStack[1024];
	// ptr to value to fill next
	char* lpNextValue;
	BOOL* lpNextBoolValue;
	int* lpNextIntValue;

}PARSERDATA_STRUCT;

static int ParseXMLConfig(CONFIG_STRUCT* cfg,char* xmlPath)
{
	DWORD dwRc = 0;
	cfg->dwLongTimeout = 10000;
	cfg->dwShortTimeout = 100;
	cfg->bRepeatAllOnTimeout = FALSE;
	cfg->pf_hello.bUsed = TRUE;
	cfg->pf_hello.bRepeat = FALSE;
	sprintf(cfg->pf_hello.path,"ivm_hello");
	cfg->pf_select.bUsed = TRUE;
	cfg->pf_select.bRepeat = FALSE;
	sprintf(cfg->pf_select.path,"ivm_choose");
	cfg->pf_redirecting.bUsed = FALSE;
	cfg->pf_redirecting.bRepeat = FALSE;
	sprintf(cfg->pf_redirecting.path,"ivm_redirecting");
	cfg->pf_sorry.bUsed = TRUE;
	cfg->pf_sorry.bRepeat = FALSE;
	sprintf(cfg->pf_sorry.path,"ivm_sorry");
	cfg->pf_timeout.bUsed = TRUE;
	cfg->pf_timeout.bRepeat = FALSE;
	sprintf(cfg->pf_timeout.path,"ivm_timeout");
	cfg->pf_internalError.bUsed = FALSE;
	cfg->pf_internalError.bRepeat = FALSE;
	cfg->pf_internalError.path[0]= '\0';
	cfg->defaultEndpoint = 0;

	cfg->menuLevel.dwItemsNum = 2;	
	cfg->menuLevel.items = (MENUITEM_STRUCT*)malloc(cfg->menuLevel.dwItemsNum*sizeof(MENUITEM_STRUCT));
	cfg->menuLevel.items[0].dwNum = 1;
	sprintf(cfg->menuLevel.items[0].playfile_path,"ivm_1-1");
	cfg->menuLevel.items[0].menuLevelItem = 0;
	cfg->menuLevel.items[0].endpointItem = (ENDPOINT_STRUCT*)malloc(sizeof(ENDPOINT_STRUCT));
	sprintf(cfg->menuLevel.items[0].endpointItem->abonent,"SIP/201");

	cfg->menuLevel.items[1].dwNum = 2;
	sprintf(cfg->menuLevel.items[1].playfile_path,"ivm_1-2");
	cfg->menuLevel.items[1].menuLevelItem = (MENULEVEL_STRUCT*)malloc(sizeof(MENULEVEL_STRUCT));
	cfg->menuLevel.items[1].endpointItem = 0;
	cfg->menuLevel.items[1].menuLevelItem->dwItemsNum = 3;
	cfg->menuLevel.items[1].menuLevelItem->items = (MENUITEM_STRUCT*)malloc(3*sizeof(MENUITEM_STRUCT));

	cfg->menuLevel.items[1].menuLevelItem->items[0].dwNum = 1;
	sprintf(cfg->menuLevel.items[1].menuLevelItem->items[0].playfile_path,"ivm_2-1");
	cfg->menuLevel.items[1].menuLevelItem->items[0].menuLevelItem = 0;
	cfg->menuLevel.items[1].menuLevelItem->items[0].endpointItem = (ENDPOINT_STRUCT*)malloc(sizeof(ENDPOINT_STRUCT));
	sprintf(cfg->menuLevel.items[1].menuLevelItem->items[0].endpointItem->abonent,"SIP/201");

	cfg->menuLevel.items[1].menuLevelItem->items[1].dwNum = 2;
	sprintf(cfg->menuLevel.items[1].menuLevelItem->items[1].playfile_path,"ivm_2-2");
	cfg->menuLevel.items[1].menuLevelItem->items[1].menuLevelItem = 0;
	cfg->menuLevel.items[1].menuLevelItem->items[1].endpointItem = (ENDPOINT_STRUCT*)malloc(sizeof(ENDPOINT_STRUCT));
	sprintf(cfg->menuLevel.items[1].menuLevelItem->items[1].endpointItem->abonent,"SIP/201");

	cfg->menuLevel.items[1].menuLevelItem->items[2].dwNum = 3;
	sprintf(cfg->menuLevel.items[1].menuLevelItem->items[2].playfile_path,"ivm_2-3");
	cfg->menuLevel.items[1].menuLevelItem->items[2].menuLevelItem = 0;
	cfg->menuLevel.items[1].menuLevelItem->items[2].endpointItem = (ENDPOINT_STRUCT*)malloc(sizeof(ENDPOINT_STRUCT));
	sprintf(cfg->menuLevel.items[1].menuLevelItem->items[2].endpointItem->abonent,"SIP/201");

/*
struct _MENULEVEL_STRUCT
{
	DWORD dwItemsNum;
	MENUITEM_STRUCT* items;
};*/
	return (int)dwRc;
}
static int ParseXMLConfig2(CONFIG_STRUCT* cfg,char* xmlPath)
{
	DWORD dwRc = 0;

	char buf[1024*16];
	DWORD len = 0;
	int delta = 0;
	int i = 0;
	int j = 0;
	FILE *my_xml = 0;
	PARSERDATA_STRUCT pData;
	XML_Parser parser;


	// init with default values
	cfg->bRepeatAllOnTimeout = FALSE;
	cfg->defaultEndpoint = 0;
	cfg->dwLongTimeout = 0;
	cfg->dwShortTimeout = 0;
	cfg->menuLevel.dwItemsNum = 0;
	cfg->menuLevel.items = 0;
	cfg->pf_hello.bRepeat = FALSE;
	cfg->pf_hello.bUsed = FALSE;
	cfg->pf_hello.path[0] = '\0';
	cfg->pf_internalError.bRepeat = FALSE;
	cfg->pf_internalError.bUsed = FALSE;
	cfg->pf_internalError.path[0] = '\0';
	cfg->pf_redirecting.bRepeat = FALSE;
	cfg->pf_redirecting.bUsed = FALSE;
	cfg->pf_redirecting.path[0] = '\0';
	cfg->pf_select.bRepeat = FALSE;
	cfg->pf_select.bUsed = FALSE;
	cfg->pf_select.path[0] = '\0';
	cfg->pf_timeout.bRepeat = FALSE;
	cfg->pf_timeout.bUsed = FALSE;
	cfg->pf_timeout.path[0] = '\0';
	cfg->pf_sorry.bRepeat = FALSE;
	cfg->pf_sorry.bUsed = FALSE;
	cfg->pf_sorry.path[0] = '\0';

	// read config
	parser = XML_ParserCreate(NULL);

	

	// read file into buffer
	memset(buf,0,sizeof(buf));
	my_xml = fopen(xmlPath, "r");
	len = (DWORD)fread(buf, 1, sizeof(buf), my_xml);
	Close(my_xml);

	// reformat buffer to get rid of unused chars
	for(i = 0; i < len; i++)
	{
		if(buf[i] == '\n')
			buf[i] = ' ';
		if(buf[i] == '\r')
			buf[i] = ' ';
		if(buf[i] == '\t')
			buf[i] = ' ';
	}

	i = 0;
	while(i < len)
	{
		if(buf[i] == '>')
		{
			int delta = 0;
			for(j = i+1; j < len; j++)
			{
				if((buf[j] == ' ')||(buf[j] == '\t')||(buf[j] == '\r')||(buf[j] == '\n'))
				{
					delta++;
				}
				else
					break;
			}
			if(delta > 0)
			{
				strcpy(&buf[i+1],&buf[i+1+delta]);
			}
			i = i + 1;
		}
		else
		if(buf[i] == '<')
		{
			int delta = 0;
			for(j = i-1; j >= 0; j--)
			{
				if((buf[j] == ' ')||(buf[j] == '\t')||(buf[j] == '\r')||(buf[j] == '\n'))
				{
					delta++;
				}
				else
					break;
			}
			if(delta > 0)
			{
				strcpy(&buf[i-delta],&buf[i]);
			}
			i = i + 1 - delta;
		}
		else
		{
			i++;
		}		
	}
	len = strlen(buf);


	// prepare parser
	
	pData.depth = 0;
	pData.cfg = cfg;
	pData.bFailed = FALSE;
	pData.bDefaultEndpointStarted = FALSE;
	pData.lpNextValue = NULL;
	pData.lpCurrentConfigItem = NULL;
	pData.lpNextBoolValue = NULL;
	pData.lpNextIntValue = NULL;
	pData.lpCurMenuLevel = NULL;
	pData.lpCurrentMenuItem = NULL;
	memset(pData.menuLevelsStack,0,sizeof(pData.menuLevelsStack));
	pData.menuLevelsStack[0] = &cfg->menuLevel;
	pData.menuLevelNum = 1;
	memset(pData.menuLevelLastDepth,0,sizeof(pData.menuLevelLastDepth));
	pData.menuLevelLastDepth[0] = 2;
	memset(pData.menuItemsStack,0,sizeof(pData.menuItemsStack));
	pData.menuItemsStack[0] = NULL;
	
	XML_SetUserData(parser, &pData);
	XML_SetElementHandler(parser, XML_startElement, XML_endElement);
	XML_SetCharacterDataHandler(parser,XML_chardata);

	// parse
    	if (XML_Parse(parser, buf, len, TRUE) == XML_STATUS_ERROR) 
	{
      		// parsing error
    	 	dwRc = 1;
    	}
	
	XML_ParserFree(parser);

	if(pData.bFailed)
		dwRc = 2;

	return (int)dwRc;
}
static void ClearConfigMenuData(MENULEVEL_STRUCT* level)
{
	int i = 0;
	if( level == 0)
		return;
	for(i = 0; i < level->dwItemsNum; i++)
	{
		if(level->items[i].endpointItem)
		{
			free(level->items[i].endpointItem);
			level->items[i].endpointItem = 0;
		}
		else
		{
			if(level->items[i].menuLevelItem)
			{
				ClearConfigMenuData(level->items[i].menuLevelItem);
				free(level->items[i].menuLevelItem);
				level->items[i].menuLevelItem = 0;
			}
		}
	}
	free(level->items);
	level->items = 0;
}
MENUITEM_STRUCT* AddItemToMenuLevel(MENULEVEL_STRUCT* lpCurMenuLevel,const char **attr)
{
	int i = 0;
	MENUITEM_STRUCT* lpNewItem = NULL;

	if(!(attr[0] && attr[1]))
		goto error;
	if(stricmp(attr[0],"digit") != 0)
		goto error;
	if(strlen(attr[1]) != 1)
		goto error;
	if((attr[1][0] != '0')&&(attr[1][0] != '1')&&(attr[1][0] != '2')&&(attr[1][0] != '3')&&(attr[1][0] != '4')&&(attr[1][0] != '5')&&(attr[1][0] != '6')&&(attr[1][0] != '7')&&(attr[1][0] != '8')&&(attr[1][0] != '9'))
		goto error;

	if(!lpCurMenuLevel->items)
	{
		lpCurMenuLevel->items = (MENUITEM_STRUCT*)malloc(1*sizeof(MENUITEM_STRUCT));
		lpCurMenuLevel->items[0].dwNum = atoi(attr[1]);
		lpCurMenuLevel->items[0].endpointItem = 0;
		lpCurMenuLevel->items[0].menuLevelItem = 0;
		lpCurMenuLevel->items[0].playfile_path[0] = '\0';
		lpCurMenuLevel->dwItemsNum++;
		lpNewItem = &lpCurMenuLevel->items[0];
	}
	else
	{
		MENUITEM_STRUCT* pOld = lpCurMenuLevel->items;
		lpCurMenuLevel->items = (MENUITEM_STRUCT*)malloc((lpCurMenuLevel->dwItemsNum+1)*sizeof(MENUITEM_STRUCT)); 
		// copy old
		for(i = 0; i < lpCurMenuLevel->dwItemsNum; i++)
		{
			lpCurMenuLevel->items[i].dwNum = pOld[i].dwNum;
			lpCurMenuLevel->items[i].endpointItem = pOld[i].endpointItem;
			lpCurMenuLevel->items[i].menuLevelItem = pOld[i].menuLevelItem;
			memcpy(lpCurMenuLevel->items[i].playfile_path,pOld[i].playfile_path,sizeof(lpCurMenuLevel->items[i].playfile_path));
		}
		// add new
		lpCurMenuLevel->items[i].dwNum = atoi(attr[1]);
		lpCurMenuLevel->items[i].endpointItem = 0;
		lpCurMenuLevel->items[i].menuLevelItem = 0;
		lpCurMenuLevel->items[i].playfile_path[0] = '\0';
		lpCurMenuLevel->dwItemsNum++;
		lpNewItem = &lpCurMenuLevel->items[i];
	}


	return lpNewItem;
error:	
	return NULL;
}
static void XMLCALL XML_startElement(void *data, const char *el, const char **attr)
{
  int i;
  PARSERDATA_STRUCT *pData = (PARSERDATA_STRUCT *)data;

  if(pData->bFailed)
	  goto error;
  
  if(pData->depth == 0)
  {
	  if(stricmp(el,"ivm") != 0)
	  {
		  pData->bFailed = TRUE;
		  goto error;
	  }
  }
  else
  if(pData->depth == 1)
  {
	  if(stricmp(el,"config") == 0)
	  {
		  if(attr[0] != 0)
		  {
			  // unexpected attribute
			  pData->bFailed = TRUE;
			  goto error;
		  }
		  pData->bConfigStarted = TRUE;
	  }
	  else
	  if(stricmp(el,"menuItemList") == 0)
	  {
		  if(attr[0] != 0)
		  {
			  // unexpected attribute
			  pData->bFailed = TRUE;
			  goto error;
		  }
		  pData->bMainMenuItemListStarted = TRUE;
	  }
	  else
	  if(stricmp(el,"defaultEndpoint") == 0)
	  {
		  if(attr[0] != 0)
		  {
			  // unexpected attribute
			  pData->bFailed = TRUE;
			  goto error;
		  }
		  pData->bDefaultEndpointStarted = TRUE; 
	  }
	  else
	  {
		  // unexpected tag
		  pData->bFailed = TRUE;
		  goto error;
	  }
  }
  else
  if(pData->depth == 2)
  {
	  if(pData->bDefaultEndpointStarted)
	  {
		  if(stricmp(el,"abonent") == 0)
		  {
			  if(attr[0] != 0)
			  {
				  // unexpected attribute
				  pData->bFailed = TRUE;
				  goto error;
			  }
			  pData->cfg->defaultEndpoint = (ENDPOINT_STRUCT*)malloc(1*sizeof(ENDPOINT_STRUCT)); 
			  pData->cfg->defaultEndpoint->abonent[0] = '\0';
			  pData->lpNextValue = pData->cfg->defaultEndpoint->abonent;
		  }
		  else
		  {
			  // unexpected tag
			  pData->bFailed = TRUE;
			  goto error;
		  }
	  }
	  else
	  if(pData->bConfigStarted)
	  {
		  if(stricmp(el,"SoundConfig") == 0)
		  {
			  const char* chAtrName = NULL;
			  const char* chAtrEnabled = NULL;

			  if((attr[0] == '\0')||(attr[1] == '\0')||(attr[2] == '\0')||(attr[3] == '\0'))
			  {
				  // attributes was expected
				  pData->bFailed = TRUE;
				  goto error;
			  }
			  if(attr[4] != '\0')
			  {
				  // more attributes was not expected
				  pData->bFailed = TRUE;
				  goto error;
			  }
			  

			  if(stricmp(attr[0],"name") == 0)
			  {
				  chAtrName = attr[1];
			  }
			  else
			  if(stricmp(attr[2],"name") == 0)
			  {
				  chAtrName = attr[3];
			  }

			  if(stricmp(attr[0],"enabled") == 0)
			  {
				  chAtrEnabled = attr[1];
			  }
			  else
			  if(stricmp(attr[2],"enabled") == 0)
			  {
				  chAtrEnabled = attr[3];
			  }

			  if(!(chAtrName && chAtrEnabled))
			  {
				  // unexpected attributes
				  pData->bFailed = TRUE;
				  goto error;
			  }

			  if((chAtrEnabled[0] != '0')&&(chAtrEnabled[0] != '1'))
			  {
				  // reformat
				  if(stricmp(chAtrEnabled,"false") == 0)
					  chAtrEnabled = "0";
				  else
				  if(stricmp(chAtrEnabled,"true") == 0)
					  chAtrEnabled = "1";
				  else
				  {
					  // unexpected value
					  pData->bFailed = TRUE;
					  goto error;
				  }
			  }

			  if(stricmp(chAtrName,"Hello") == 0)
			  {
				  if(chAtrEnabled[0] == '0')
					pData->cfg->pf_hello.bUsed = FALSE;
				  else
				  if(chAtrEnabled[0] == '1')
					pData->cfg->pf_hello.bUsed = TRUE;
				  else
				  {
					  // unexpected value
					  pData->bFailed = TRUE;
					  goto error;
				  }
				  pData->lpCurrentConfigItem = &pData->cfg->pf_hello;
			  }
			  else
			  if(stricmp(chAtrName,"SelectBranch") == 0)
			  {
				  if(chAtrEnabled[0] == '0')
					  pData->cfg->pf_select.bUsed = FALSE;
				  else
				  if(chAtrEnabled[0] == '1')
					pData->cfg->pf_select.bUsed = TRUE;
				  else
				  {
					  // unexpected value
					  pData->bFailed = TRUE;
					  goto error;
				  }
				  pData->lpCurrentConfigItem = &pData->cfg->pf_select;
			  }
			  else
			  if(stricmp(chAtrName,"Timeout") == 0)
			  {
				  if(chAtrEnabled[0] == '0')
					  pData->cfg->pf_timeout.bUsed = FALSE;
				  else
				  if(chAtrEnabled[0] == '1')
					pData->cfg->pf_timeout.bUsed = TRUE;
				  else
				  {
					  // unexpected value
					  pData->bFailed = TRUE;
					  goto error;
				  }
				  pData->lpCurrentConfigItem = &pData->cfg->pf_timeout;
			  }
			  else
			  if(stricmp(chAtrName,"ErrorOnRedirect") == 0)
			  {
				  if(chAtrEnabled[0] == '0')
					  pData->cfg->pf_sorry.bUsed = FALSE;
				  else
				  if(chAtrEnabled[0] == '1')
					pData->cfg->pf_sorry.bUsed = TRUE;
				  else
				  {
					  // unexpected value
					  pData->bFailed = TRUE;
					  goto error;
				  }
				  pData->lpCurrentConfigItem = &pData->cfg->pf_sorry;
			  }
			  else
			  if(stricmp(chAtrName,"Redirecting") == 0)
			  {
				  if(chAtrEnabled[0] == '0')
					  pData->cfg->pf_redirecting.bUsed = FALSE;
				  else
				  if(chAtrEnabled[0] == '1')
					pData->cfg->pf_redirecting.bUsed = TRUE;
				  else
				  {
					  // unexpected value
					  pData->bFailed = TRUE;
					  goto error;
				  }
				  pData->lpCurrentConfigItem = &pData->cfg->pf_redirecting;
			  }
			  else
			  if(stricmp(chAtrName,"InternalError") == 0)
			  {
				  if(chAtrEnabled[0] == '0')
					  pData->cfg->pf_internalError.bUsed = FALSE;
				  else
				  if(chAtrEnabled[0] == '1')
					pData->cfg->pf_internalError.bUsed = TRUE;
				  else
				  {
					  // unexpected value
					  pData->bFailed = TRUE;
					  goto error;
				  }
				  pData->lpCurrentConfigItem = &pData->cfg->pf_internalError;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(stricmp(el,"TimeoutConfig") == 0)
		  {
			  const char* chAtrName = NULL;
			  if((attr[0] == '\0')||(attr[1] == '\0'))
			  {
				  // attribute was expected
				  pData->bFailed = TRUE;
				  goto error;
			  }
			  if(attr[2] != '\0')
			  {
				  // more attributes was not expected
				  pData->bFailed = TRUE;
				  goto error;
			  }
			  

			  if(stricmp(attr[0],"name") == 0)
			  {
				  chAtrName = attr[1];
			  }

			  if(!chAtrName)
			  {
				  // unexpected attributes
				  pData->bFailed = TRUE;
				  goto error;
			  }

			  if(stricmp(chAtrName,"ShortTimeout") == 0)
			  {
				  pData->lpCurrentConfigItem = &pData->cfg->dwShortTimeout;
			  }
			  else
			  if(stricmp(chAtrName,"LongTimeout") == 0)
			  {
				  pData->lpCurrentConfigItem = &pData->cfg->dwLongTimeout;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(stricmp(el,"ActionConfig") == 0)
		  {
			  const char* chAtrName = NULL;
			  if((attr[0] == '\0')||(attr[1] == '\0'))
			  {
				  // attribute was expected
				  pData->bFailed = TRUE;
				  goto error;
			  }
			  if(attr[2] != '\0')
			  {
				  // more attributes was not expected
				  pData->bFailed = TRUE;
				  goto error;
			  }
			  
			  if(stricmp(attr[0],"name") == 0)
			  {
				  chAtrName = attr[1];
			  }

			  if(!chAtrName)
			  {
				  // unexpected attributes
				  pData->bFailed = TRUE;
				  goto error;
			  }

			  if(stricmp(chAtrName,"RepeatAllOnTimeout") == 0)
			  {
				  pData->lpCurrentConfigItem = &pData->cfg->bRepeatAllOnTimeout;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  {
			  // unexpected tag
			  pData->bFailed = TRUE;
			  goto error;
		  }
	  }
	  else
	  if(pData->bMainMenuItemListStarted)
	  {
		  if(stricmp(el,"item") == 0)
		  {
			  pData->lpCurMenuLevel = &pData->cfg->menuLevel;
			  pData->lpCurrentMenuItem = AddItemToMenuLevel(pData->lpCurMenuLevel,attr);
			  if(pData->lpCurrentMenuItem == 0)
			  {
				  // inner fail
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  {
			  // unexpected tag
			  pData->bFailed = TRUE;
			  goto error;
		  }
	  }
	  else
	  {
		  // unexpected tag
		  pData->bFailed = TRUE;
		  goto error;
	  }
  }
  else
  if(pData->depth == 3)
  {
	  if(pData->bConfigStarted)
	  {
		  if(!pData->lpCurrentConfigItem)
		  {
			  // internal error
			  pData->bFailed = TRUE;
			  goto error;
		  }
		  if(pData->lpCurrentConfigItem == &pData->cfg->pf_hello)
		  {
			  if(stricmp(el,"file") == 0)
			  {
				  pData->lpNextValue = pData->cfg->pf_hello.path;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->pf_select)
		  {
			  if(stricmp(el,"file") == 0)
			  {
				  pData->lpNextValue = pData->cfg->pf_select.path;
			  }
			  else
			  if(stricmp(el,"repeat") == 0)
			  {
				  pData->lpNextBoolValue = &pData->cfg->pf_select.bRepeat;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->pf_timeout)
		  {
			  if(stricmp(el,"file") == 0)
			  {
				  pData->lpNextValue = pData->cfg->pf_timeout.path;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->pf_sorry)
		  {
			  if(stricmp(el,"file") == 0)
			  {
				  pData->lpNextValue = pData->cfg->pf_sorry.path;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->pf_redirecting)
		  {
			  if(stricmp(el,"file") == 0)
			  {
				  pData->lpNextValue = pData->cfg->pf_redirecting.path;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->pf_internalError)
		  {
			  if(stricmp(el,"file") == 0)
			  {
				  pData->lpNextValue = pData->cfg->pf_internalError.path;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->dwShortTimeout)
		  {
			  if(stricmp(el,"value") == 0)
			  {
				  pData->lpNextIntValue = (int*)&pData->cfg->dwShortTimeout;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->dwLongTimeout)
		  {
			  if(stricmp(el,"value") == 0)
			  {
				  pData->lpNextIntValue = (int*)&pData->cfg->dwLongTimeout;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(pData->lpCurrentConfigItem == &pData->cfg->bRepeatAllOnTimeout)
		  {
			  if(stricmp(el,"value") == 0)
			  {
				  pData->lpNextBoolValue = (int*)&pData->cfg->bRepeatAllOnTimeout;
			  }
			  else
			  {
				  // unexpected tag
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  {
			  // unexpected tag
              pData->bFailed = TRUE;
              goto error;
		  }
	  }
	  else
	  if(pData->bMainMenuItemListStarted)
	  {
		  if(stricmp(el,"file") == 0)
		  {
			  pData->lpNextValue = pData->lpCurrentMenuItem->playfile_path;
		  }
		  else
		  if(stricmp(el,"endpoint") == 0)
		  {
			  pData->lpCurrentMenuItem->endpointItem = (ENDPOINT_STRUCT*)malloc(1*sizeof(ENDPOINT_STRUCT)); 
			  pData->lpCurrentMenuItem->endpointItem->abonent[0] = '\0';
		  }
		  else
		  if(stricmp(el,"menuItemList") == 0)
		  {
			  pData->lpCurrentMenuItem->menuLevelItem = (MENULEVEL_STRUCT*)malloc(1*sizeof(MENULEVEL_STRUCT));
			  pData->lpCurrentMenuItem->menuLevelItem->dwItemsNum = 0;
			  pData->lpCurrentMenuItem->menuLevelItem->items = 0;
			  pData->lpCurMenuLevel = pData->lpCurrentMenuItem->menuLevelItem;
			  pData->menuLevelsStack[1] = pData->lpCurMenuLevel;
			  pData->menuItemsStack[1] = pData->lpCurrentMenuItem;
			  pData->menuLevelLastDepth[1] = 3;
			  pData->menuLevelNum = 2;
		  }
	  }
	  else
	  {
		  // unexpected tag
		  pData->bFailed = TRUE;
		  goto error;
	  }
  }
  else
  {
	  int depth = pData->depth;

	  if(pData->bMainMenuItemListStarted)
	  {
		  // check level
		  if(pData->menuLevelLastDepth[pData->menuLevelNum-1] >= depth)
		  {
			  int prevLvl = 0;
			  int i = 0;
			  for(i = 0; i < pData->menuLevelNum; i++)
			  {
				  if(pData->menuLevelLastDepth[i] > depth)
				  {
					  prevLvl = i-1;
					  break;
				  }
			  }
			  pData->lpCurMenuLevel = pData->menuLevelsStack[prevLvl];
			  pData->lpCurrentMenuItem = &pData->lpCurMenuLevel->items[pData->lpCurMenuLevel->dwItemsNum-1];//pData->menuItemsStack[prevLvl];
			  pData->menuLevelNum = prevLvl+1;
		  }

		  if(stricmp(el,"abonent") == 0)
		  {
			  pData->lpNextValue = pData->lpCurrentMenuItem->endpointItem->abonent;
		  }
		  else
		  if(stricmp(el,"item") == 0)
		  {
			  //pData->lpCurMenuLevel = &pData->cfg->menuLevel;
			  pData->lpCurrentMenuItem = AddItemToMenuLevel(pData->lpCurMenuLevel,attr);
			  if(pData->lpCurrentMenuItem == 0)
			  {
				  // inner fail
				  pData->bFailed = TRUE;
				  goto error;
			  }
		  }
		  else
		  if(stricmp(el,"menuItemList") == 0)
		  {
			  int rrr = 0;
			  pData->lpCurrentMenuItem->menuLevelItem = (MENULEVEL_STRUCT*)malloc(1*sizeof(MENULEVEL_STRUCT));
			  pData->lpCurMenuLevel = pData->lpCurrentMenuItem->menuLevelItem;
			  pData->menuLevelsStack[pData->menuLevelNum] = pData->lpCurMenuLevel;
			  pData->menuLevelLastDepth[pData->menuLevelNum] = depth;
			  pData->menuItemsStack[pData->menuLevelNum] = pData->lpCurrentMenuItem;
			  pData->menuLevelNum++;
		  }
		  else
		  if(stricmp(el,"file") == 0)
		  {
			  pData->lpNextValue = pData->lpCurrentMenuItem->playfile_path;
		  }
		  else
		  if(stricmp(el,"endpoint") == 0)
		  {
			  pData->lpCurrentMenuItem->endpointItem = (ENDPOINT_STRUCT*)malloc(1*sizeof(ENDPOINT_STRUCT)); 
			  pData->lpCurrentMenuItem->endpointItem->abonent[0] = '\0';
		  }
		  else
		  if(stricmp(el,"menuItemList") == 0)
		  {
			  pData->lpCurrentMenuItem->menuLevelItem = (MENULEVEL_STRUCT*)malloc(1*sizeof(MENULEVEL_STRUCT));
			  pData->lpCurMenuLevel = pData->lpCurrentMenuItem->menuLevelItem;
		  }
		  else
		  {
			  // unexpected tag
			  pData->bFailed = TRUE;
			  goto error;
		  }
	  }
	  else
	  {
		  // unexpected tag
		  pData->bFailed = TRUE;
		  goto error;
	  }
  }


error:
  pData->depth += 1;
}

static void XMLCALL XML_endElement(void *data, const char *el)
{

  PARSERDATA_STRUCT *pData = (PARSERDATA_STRUCT *)data;
  if(pData->bFailed)
	  goto error;

  if(pData->depth == 2)
  {
	  if(stricmp(el,"config") == 0)
	  {
		  pData->bConfigStarted = FALSE;
	  }
	  else
	  if(stricmp(el,"menuItemList") == 0)
	  {
		  pData->bMainMenuItemListStarted = FALSE;
	  }
	  else
	  if(stricmp(el,"defaultEndpoint") == 0)
	  {
		  pData->bDefaultEndpointStarted = FALSE;
		  pData->lpNextValue = NULL;
	  }
  }
error:
  pData->depth -= 1;
}

static void XMLCALL XML_chardata(void *data, const XML_Char *s, int len)
{
  PARSERDATA_STRUCT *pData = (PARSERDATA_STRUCT *)data;
  if(pData->bFailed)
	  goto error;

  if(pData->lpNextValue)
  {
	  memcpy(pData->lpNextValue,s,len);
	  pData->lpNextValue[len] = '\0';
	  pData->lpNextValue = NULL;
  }
  else
  if(pData->lpNextBoolValue)
  {
	  if(len == 1)
	  {	  
		  if(s[0] == '0')
			  *(pData->lpNextBoolValue) = FALSE;
		  else
		  if(s[0] == '1')
			  *(pData->lpNextBoolValue) = TRUE;
		  else
		  {
			  // format error
			  pData->bFailed = TRUE;
			  goto error;
		  }
	  }
	  else
	  if(len == 4)
	  {
		  if(stricmp(s,"true") == 0)
			  *(pData->lpNextBoolValue) = TRUE;
		  else
		  {
			  // format error
			  pData->bFailed = TRUE;
			  goto error;
		  }
	  }
	  else
	  if(len == 5)
	  {
		  if(stricmp(s,"false") == 0)
			  *(pData->lpNextBoolValue) = FALSE;
		  else
		  {
			  // format error
			  pData->bFailed = TRUE;
			  goto error;
		  }
	  }
	  else
	  {
		   // format error
			  pData->bFailed = TRUE;
			  goto error;
	  }

	  pData->lpNextBoolValue = NULL;
  }
  else
  if(pData->lpNextIntValue)
  {
	  char buf[128];
	  int i = 0;
	  BOOL bFormatError = FALSE;

	  memset(buf,0,sizeof(buf));
	  memcpy(buf,s,len);
	  
	  for(i = 0; i < len; i++)
	  {
		  if((buf[i] != '0')&&(buf[i] != '1')&&(buf[i] != '2')&&(buf[i] != '3')&&(buf[i] != '4')&&(buf[i] != '5')&&(buf[i] != '6')&&(buf[i] != '7')&&(buf[i] != '8')&&(buf[i] != '9'))
		  {
			  bFormatError = TRUE;
			  break;
		  }
	  }
	  if(bFormatError)
	  {
		  // format error
		  pData->bFailed = TRUE;
		  goto error;
	  }

	  *(pData->lpNextIntValue) = atoi(buf);
	  pData->lpNextIntValue = NULL;
  }
  
  error:
  ;
}
// asterisk strange definitions


AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ivm",
		.load = OnLoad,
		.unload = OnUnLoad,
	       );

